package com.zzg.mybatis.generator.util;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

import com.zzg.mybatis.generator.model.ColumnEntity;
import com.zzg.mybatis.generator.model.GeneratorConfig;
import com.zzg.mybatis.generator.model.ItemInfo;
import com.zzg.mybatis.generator.model.TableEntity;

import cn.hutool.core.io.FileUtil;

import java.io.File;
import java.io.FileOutputStream;
import java.io.StringWriter;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 代码生成器   工具类
 *
 * @author chenshun
 * @email sunlightcs@gmail.com
 * @date 2016年12月19日 下午11:40:24
 */
public class GenUtils {

    public static List<String> getTemplates() {
        List<String> templates = new ArrayList<String>();
        //使用自定义模板
        templates.add("template/rzx/DO.java.vm");
        templates.add("template/rzx/DAOProvider.java.vm");

        return templates;
    }
    
    public static Map<String, String> getTempl() {
    	Map<String, String> map = new HashMap<>();
    	map.put("DO", "template/rzx/DO.java.vm");
    	map.put("Provider", "template/rzx/Provider.java.vm");
    	map.put("Mapper", "template/rzx/Mapper.java.vm");
    	
    	map.put("Dto", "template/rzx/DTO.java.vm");
    	map.put("Converter", "template/rzx/Converter.java.vm");
    	map.put("Service", "template/rzx/Service.java.vm");
    	map.put("ServiceImpl", "template/rzx/ServiceImpl.java.vm");
    	
    	map.put("RequestSave", "template/rzx/RequestSave.java.vm");
    	map.put("RequestUpdate", "template/rzx/RequestUpdate.java.vm");
    	map.put("Response", "template/rzx/Response.java.vm");
    	map.put("Exception", "template/rzx/Exception.java.vm");
    	map.put("Controller", "template/rzx/Controller.java.vm");
    	
    	return map;
    }
    
    public static Map<String, ItemInfo> itemMap = new HashMap<>();
    
    /**
     * 生成代码
     */
    public static void generatorCode(TableEntity table, String moduleName, String packageName, String author, 
    		List<ColumnEntity> columns, GeneratorConfig generatorConfig, List<ColumnEntity> ceEqualList, 
    		List<ColumnEntity> ceInList, List<ColumnEntity> ceLikeList, List<ColumnEntity> ceAllList) {
        //配置信息
        Configuration config = getConfig();
        boolean hasBigDecimal = false, hasDate = false;
        //表信息
        TableEntity tableEntity = new TableEntity();
        tableEntity.setTableName(table.getTableName());
        String comments = table.getComments();
        comments = comments.endsWith("表") ? comments.substring(0, comments.length() - 1) : comments;
        tableEntity.setComments(comments);
        tableEntity.setOriginalTableName(table.getOriginalTableName());
        
        //表名转换成Java类名
        String className = tableToJava(tableEntity.getTableName(), config.getString("tablePrefix"));
        tableEntity.setClassName(className);
        tableEntity.setClassname(StringUtils.uncapitalize(className));
        //
        String tb = generatorConfig.getTb();
        String SQLUtilPackage = generatorConfig.getSQLUtilPackage();
        String DOPackage = generatorConfig.getDomPackage();
        String DTOPackage = generatorConfig.getDtoPackage();
        String ProviderPackage = generatorConfig.getProviderPackage();
        
        String MapperPackage = generatorConfig.getMapperPackage();
        String ServicePackage = generatorConfig.getServicePackage();
        String ConverterPackage = generatorConfig.getConverterPackage();
        String ServiceImplPackage = generatorConfig.getServiceImplPackage();
        
        String RequestSavePackage = generatorConfig.getRequestSavePackage();
        String RequestUpdatePackage = generatorConfig.getRequestUpdatePackage();
        String ResponsePackage = generatorConfig.getResponsePackage();
        String ExceptionPackage = generatorConfig.getExceptionPackage();
        
        String cv = generatorConfig.getCv();
        String ep = generatorConfig.getEp();
        String ct = generatorConfig.getCt();
        String rp = generatorConfig.getRp();
        String apisort = generatorConfig.getApisort();

        //过滤公共属性
        columns = filterCommonProps(columns, Boolean.TRUE);
        
        //列信息
        List<ColumnEntity> columsList = new ArrayList<>();
        for (ColumnEntity column : columns) {
            ColumnEntity columnEntity = new ColumnEntity();
            columnEntity.setColumnName(column.getColumnName());
            columnEntity.setDataType(column.getDataType());
            columnEntity.setComments(column.getComments());
            //列名转换成Java属性名
            String attrName = columnToJava(columnEntity.getColumnName());
            columnEntity.setAttrName(attrName);
            columnEntity.setAttrname(StringUtils.uncapitalize(attrName));
            //列的数据类型，转换成Java类型
            String attrType = config.getString(columnEntity.getDataType(), "unknowType");
            columnEntity.setAttrType(attrType);
            if (!hasBigDecimal && attrType.equals("BigDecimal")) {
                hasBigDecimal = true;
            }
            if (!hasDate && attrType.equals("Date")) {
            	hasDate = true;
            }
            columsList.add(columnEntity);
        }
        tableEntity.setColumns(columsList);

        //设置velocity资源加载器
        Properties prop = new Properties();
        prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        //prop.put("directive.foreach.counter.initial.value", 0);
        //prop.put(RuntimeConstants.RESOURCE_LOADER, "classpath");
        Velocity.init(prop);
        
        Map<String, String> map2Templ = getTempl();
        for(Map.Entry<String, String> entry : map2Templ.entrySet()) {
        	String code = entry.getKey(), templ = entry.getValue();
        	String packagePath = itemMap.get(code).getPackagePath();
        	//过滤未填包名的类
        	if(StringUtils.isBlank(packagePath)) { continue; }
        	
        	//构建模版数据
        	VelocityContext context = getVelocityContext(author, itemMap.get(code).getPackagePath(),
        			tb, cv, ep, ct, rp, apisort, SQLUtilPackage, DOPackage, DTOPackage, ProviderPackage,
        			MapperPackage, ServicePackage, ConverterPackage, ServiceImplPackage,
        			RequestSavePackage, RequestUpdatePackage, ResponsePackage, ExceptionPackage,
        			hasBigDecimal, hasDate, tableEntity, ceEqualList, ceInList, ceLikeList, ceAllList);
        	//渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(templ, "UTF-8");
            tpl.merge(context, sw);
            File file = FileUtil.touch(itemMap.get(code).getFilePath(), itemMap.get(code).getClassName()+".java");
        	try {
				IOUtils.write(sw.toString(), new FileOutputStream(file), "UTF-8");
				IOUtils.closeQuietly(sw);
			} catch (Exception e) {
				throw new RRException("渲染模板失败，表名：" + tableEntity.getTableName(), e);
			}
        };
    }


    private static final List<String> defaultCommProps = new ArrayList<>(
    		Arrays.asList("IS_DELETE", "CREATE_AT", "CREATE_BY", "UPDATE_AT", "UPDATE_BY"));
    private static final String DB_SORT = "SORT";
    private static final String DB_EPID = "EPID";
    private static final String DB_PROJECT_ID = "PROJECT_ID";
    private static final String DB_SECTION_ID = "SECTION_ID";
    private static final String DB_WBS_ID = "WBS_ID";
    private static final String DB_PARENT_ID = "PARENT_ID";
    private static final List<String> DB_SORTDETERMINER= Arrays.asList(DB_EPID, DB_PROJECT_ID, DB_SECTION_ID, DB_WBS_ID, DB_PARENT_ID);
    private static List<ColumnEntity> filterCommonProps(List<ColumnEntity> colums, Boolean isFilter) {
    	List<ColumnEntity> columsList = new ArrayList<>();
    	if(!isFilter || CollectionUtils.isEmpty(colums)) { return columsList; }
    	
    	columsList = colums.stream().filter(item -> !defaultCommProps.contains(item.getColumnName().toUpperCase()))
    			.collect(Collectors.toList());
    	
    	return columsList;
	}
    private static Boolean isContainProp(TableEntity tableEntity, String prop) {
    	if(null != tableEntity && CollectionUtils.isNotEmpty(tableEntity.getColumns())) {
    		List<String> dbColumns = tableEntity.getColumns().stream().map(obj -> obj.getColumnName().toUpperCase()).collect(Collectors.toList());
    		if(dbColumns.contains(prop)) { return Boolean.TRUE; }
    	}
    	return Boolean.FALSE;
    }
    private static List<ColumnEntity> containPropList(TableEntity tableEntity) {
    	List<ColumnEntity> props = new ArrayList<>();
    	if(null != tableEntity && CollectionUtils.isNotEmpty(tableEntity.getColumns())) {
    		List<String> dbColumns = tableEntity.getColumns().stream().map(obj -> obj.getColumnName().toUpperCase()).collect(Collectors.toList());
    		Map<String, ColumnEntity> columnEntityMap = tableEntity.getColumns().stream()
    				.collect(Collectors.toMap(ColumnEntity::getColumnName2Upper, Function.identity(), (key1, key2) -> key2));
    		for(String prop : DB_SORTDETERMINER) {
    			if(dbColumns.contains(prop)) { props.add(columnEntityMap.get(prop)); }
    		}
    	}
    	return props;
    }

	private static VelocityContext getVelocityContext(String author, String packagePath, String tb, String cv, String ep, String ct, String rp, String apisort,
			String SQLUtilPackage, String DOPackage, String DTOPackage, String ProviderPackage,
			String MapperPackage, String ServicePackage, String ConverterPackage, String ServiceImplPackage,
			String RequestSavePackage, String RequestUpdatePackage, String ResponsePackage, String ExceptionPackage,
			Boolean hasBigDecimal, Boolean hasDate, TableEntity tableEntity, List<ColumnEntity> ceEqualList, 
    		List<ColumnEntity> ceInList, List<ColumnEntity> ceLikeList, List<ColumnEntity> ceAllList) {
		if(CollectionUtils.isEmpty(tableEntity.getColumns())) { tableEntity.setColumns(new ArrayList<>()); }
		
		ceAllList = CollectionUtils.isNotEmpty(ceAllList) ? ceAllList : new ArrayList<>();
		List<ColumnEntity> ceAllList4Page = new ArrayList<>();
		if(CollectionUtils.isNotEmpty(ceAllList)) { ceAllList4Page.addAll(ceAllList); }
		ceAllList4Page.addAll(ColumnEntity.getColumnEntity4Page());
		
		List<ColumnEntity> columns4DTO2 = new ArrayList<>();
		if(CollectionUtils.isNotEmpty(tableEntity.getColumns())) { columns4DTO2.addAll(tableEntity.getColumns()); }
		columns4DTO2.addAll(ColumnEntity.getColumnEntity4DTO2());
		
		List<ColumnEntity> columns4DTO3 = new ArrayList<>();
		if(CollectionUtils.isNotEmpty(tableEntity.getColumns())) { columns4DTO3.addAll(tableEntity.getColumns()); }
		columns4DTO3.addAll(ColumnEntity.getColumnEntity4DTO3());
		
		List<ColumnEntity> columns4Save2DTO = new ArrayList<>();
		if(CollectionUtils.isNotEmpty(tableEntity.getColumns())) { columns4Save2DTO.addAll(tableEntity.getColumns()); }
		columns4Save2DTO.addAll(ColumnEntity.getColumns4Save2DTO());
		
		List<ColumnEntity> columns4Update2DTO = new ArrayList<>();
		if(CollectionUtils.isNotEmpty(tableEntity.getColumns())) { columns4Update2DTO.addAll(tableEntity.getColumns()); }
		columns4Update2DTO.addAll(ColumnEntity.getColumns4Update2DTO());
		
		List<ColumnEntity> columns4Response = new ArrayList<>();
		if(CollectionUtils.isNotEmpty(tableEntity.getColumns())) { columns4Response.addAll(tableEntity.getColumns()); }
		columns4Response.addAll(ColumnEntity.getColumns4Response2());
		
		List<ColumnEntity> columns4Response2 = new ArrayList<>();
		if(CollectionUtils.isNotEmpty(tableEntity.getColumns())) { columns4Response2.addAll(tableEntity.getColumns()); }
		columns4Response2.addAll(ColumnEntity.getColumns4Response2());
		
		
		
    	//封装模板数据
        Map<String, Object> map = new HashMap<>();
        map.put("package", packagePath);
        map.put("tb", tb);
        map.put("cv", cv);
        map.put("ep", ep);
        map.put("ct", ct);
        map.put("rp", rp);
        map.put("apisort", apisort);
        map.put("SQLUtilPackage", SQLUtilPackage);
        map.put("ProviderPackage", ProviderPackage);
        map.put("DOPackage", DOPackage);
        map.put("DTOPackage", DTOPackage);
        
        map.put("MapperPackage", MapperPackage);
        map.put("ServicePackage", ServicePackage);
        map.put("ConverterPackage", ConverterPackage);
        map.put("ServiceImplPackage", ServiceImplPackage);

        map.put("RequestSavePackage", RequestSavePackage);
        map.put("RequestUpdatePackage", RequestUpdatePackage);
        map.put("ResponsePackage", ResponsePackage);
        map.put("ExceptionPackage", ExceptionPackage);
        
        map.put("tableName", tableEntity.getOriginalTableName());
        map.put("comments", tableEntity.getComments());
        map.put("className", tableEntity.getClassName());
        map.put("classname", tableEntity.getClassname());
        map.put("pathName", tableEntity.getClassname().toLowerCase());
        map.put("columns", tableEntity.getColumns());
        map.put("hasBigDecimal", hasBigDecimal);
        map.put("hasDate", hasDate);
        map.put("package", packagePath);
        map.put("author", StringUtils.isNotBlank(author) ? author : "");
        map.put("datetime", DateUtils.format(new Date(), DateUtils.DATE_TIME_PATTERN));
        
        map.put("ceAllList", ceAllList);
        map.put("ceEqualList", ceEqualList);
        map.put("ceInList", ceInList);
        map.put("ceLikeList", ceLikeList);
        map.put("isContainLike", CollectionUtils.isNotEmpty(ceLikeList) ? Boolean.TRUE : Boolean.FALSE);
        
        map.put("isContainSort", isContainProp(tableEntity, DB_SORT));
        //map.put("isContainSort", Boolean.TRUE);
        map.put("isContainEpid", isContainProp(tableEntity, DB_EPID));
        map.put("isContainProjectId", isContainProp(tableEntity, DB_PROJECT_ID));
        map.put("isContainSectionId", isContainProp(tableEntity, DB_SECTION_ID));
        map.put("isContainWbsId", isContainProp(tableEntity, DB_WBS_ID));
        map.put("isContainParentId", isContainProp(tableEntity, DB_PARENT_ID));
        
        map.put("determinerList4Basic", containPropList(tableEntity));
        map.put("determinerList4BasicProps", containPropList(tableEntity).stream().map(ColumnEntity::getColumnName).distinct().collect(Collectors.toList()));
        
        map.put("determinerList4List", ColumnEntity.converter2Str(ceAllList));
        map.put("determinerList4Page", ColumnEntity.converter2Str(ceAllList4Page));
        
        map.put("columns4Save", ColumnEntity.filter4Save(tableEntity.getColumns()));
        map.put("columns4Update", ColumnEntity.filter4Update(tableEntity.getColumns()));
        //map.put("columns4Response", tableEntity.getColumns());
        map.put("columns4Response", columns4Response);
        map.put("columns4Response2", columns4Response2);
        
        map.put("columns4Save2DTO", columns4Save2DTO);
        map.put("columns4Update2DTO", columns4Update2DTO);
        
        map.put("columns4DTO", tableEntity.getColumns());
        map.put("columns4DTO2", columns4DTO2);
        map.put("columns4DTO3", columns4DTO3);
        
        return new VelocityContext(map);
	}

	/**
     * 列名转换成Java属性名
     */
    public static String columnToJava(String columnName) {
        return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", "");
    }
    
	/**
     * 列名转换成Java属性名
     */
	private static final String EPID = "epid";
    public static String columnToJava(String columnName, Boolean isFilter) {
    	if(isFilter) {
    		if(EPID.equalsIgnoreCase(columnName)) { return EPID; }
    	}
        return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", "");
    }

    /**
     * 表名转换成Java类名
     */
    public static String tableToJava(String tableName, String tablePrefix) {
        if (StringUtils.isNotBlank(tablePrefix)) {
            tableName = tableName.replaceFirst(tablePrefix, "");
        }
        return columnToJava(tableName);
    }

    /**
     * 获取配置信息
     */
    public static Configuration getConfig() {
        try {
            return new PropertiesConfiguration("generator.properties");
        } catch (ConfigurationException e) {
            throw new RRException("获取配置文件失败，", e);
        }
    }

}
